package net.yla.smartbus.module.ba.right.login.service.impl;

import net.yla.smartbus.core.api.RequestCode;
import net.yla.smartbus.core.base.BaseService;
import net.yla.smartbus.core.config.ProjectConstant;
import net.yla.smartbus.core.constants.CacheKeyPrefix;
import net.yla.smartbus.core.exception.AppException;
import net.yla.smartbus.core.util.IPUtil;
import net.yla.smartbus.core.util.RedisUtil;
import net.yla.smartbus.core.util.ShiroUtils;
import net.yla.smartbus.core.util.StringEx;
import net.yla.smartbus.module.ba.right.account.entity.AccountsDO;
import net.yla.smartbus.module.ba.right.account.entity.LoginLogsDO;
import net.yla.smartbus.module.ba.right.account.entity.LoginTracesDO;
import net.yla.smartbus.module.ba.right.account.service.AccountsService;
import net.yla.smartbus.module.ba.right.account.service.LoginLogsService;
import net.yla.smartbus.module.ba.right.account.service.LoginTracesService;
import net.yla.smartbus.module.ba.right.admin.entity.CaptchasDO;
import net.yla.smartbus.module.ba.right.admin.service.CaptchasService;
import net.yla.smartbus.module.ba.right.login.dto.LoginDTO;
import net.yla.smartbus.module.ba.right.login.dto.UserSession;
import net.yla.smartbus.module.ba.right.login.dto.UserSessionReturnDTO;
import net.yla.smartbus.module.ba.right.login.form.LoginForm;
import net.yla.smartbus.module.ba.right.login.service.AccountLoginService;
import net.yla.smartbus.module.ba.right.login.service.AdminLoginService;
import net.yla.smartbus.module.ba.right.right.service.RightsService;
import net.yla.smartbus.module.ba.right.roles.service.RolesService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @Date: 2020-08-12 15:20
 * @Author: Being Nobody
 * @Version: 1.0
 */
@Service
public class AccountLoginServiceImpl extends BaseService implements AccountLoginService {
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private LoginTracesService loginTracesService;
    @Autowired
    private AccountsService accountService;
    @Autowired
    private CaptchasService captchasService;
    @Autowired
    private LoginLogsService loginLogsService;
    @Autowired
    private RolesService rolesService;
    @Autowired
    private RightsService rightsService;


    /**
     * 用户登录
     *
     * @param form
     * @return
     */
    @Override
    public LoginDTO passwordLogin(LoginForm form, String ip, String userAgent) {
        LoginDTO loginDTO = null;
        // 一、IP跟踪表信息处理
        long ipL = IPUtil.ipv4ToLong(ip);
        LocalDateTime currentTime = LocalDateTime.now();
        LoginLogsDO loginLogs = this.loginLog(form, ipL, userAgent);
        // 获取跟踪信息
        LoginTracesDO traces = loginTracesService.selectByLoginIP(ipL);
        Duration between = null;
        // 不存在
        if (traces != null) {
            between = Duration.between(traces.getLastErrorTime(), currentTime);
            // 大于阀值，且在5分钟内
            Integer errorCount = traces.getErrorCount();
            long min = between.toMinutes();
            if (errorCount >= ProjectConstant.IPERRORCOUNT && min < ProjectConstant.IPERRORMINUTE) {
                // loginstate = 3 安全性验证失败
                loginLogs.setLoginState(3);
                this.recordLog(loginLogs);
                throw new AppException("请" + (ProjectConstant.IPERRORMINUTE - min) + "分钟之后再次登录", RequestCode.Form_Validate_Error);
            }
        }
        // 存在
        else {
            // 记录跟踪记录表 ip
            traces = new LoginTracesDO();
            traces.setIp(ipL);
            loginTracesService.insertSelective(traces);
        }
        // 二、管理员表信息处理
        // 获取用户
        AccountsDO account = accountService.selectByLoginId(form.getLoginId());
        if (account == null) {
            // 账号名不存在，输出登录失败。因为账号不存在，所以不记录日志。
            throw new AppException("账号名不存在", RequestCode.Form_Validate_Error);
        }
        between = Duration.between(account.getLastModifyTime(), currentTime);
        if (account.getState() == 4) { //禁用
            loginLogs.setLoginState(5);
            loginLogs.setAccountId(account.getAccountId());
            this.recordLog(loginLogs);
            throw new AppException("账号已禁用", RequestCode.Form_Validate_Error);
        }
        if (account.getState() == 3 && between.toMinutes() < ProjectConstant.LOCKEDMINUTE) { //锁定
            loginLogs.setAccountId(account.getAccountId());
            this.recordLog(loginLogs);
            throw new AppException("账号已锁定!请" + (ProjectConstant.LOCKEDMINUTE - between.toMinutes()) + "分钟之后再次登录", RequestCode.Form_Validate_Error);
        }
        // 验证码错误次数 图片验证码
        // 获取验证码信息
        CaptchasDO captchas = captchasService.getByIP(ipL);
        // TODO 1没有考虑短信验证码 2图片验证码获取需要时间间隔吗
        if (captchas != null && account.getState() != 0) {
//            between = Duration.between(captchas.getLastModifyTime(), now);
            if (StringUtils.isEmpty(form.getCaptcha())) {
                loginLogs.setLoginState(3);
                this.recordLog(loginLogs);
                throw new AppException("请输入验证码", RequestCode.Form_Validate_Error);
            }
            if (form.getCaptcha().equals(captchas.getCode())) {
                loginLogs.setLoginState(3);
                this.recordLog(loginLogs);
                captchas.setRetryCount(captchas.getRetryCount() + 1);
                captchasService.updateByPrimaryKeySelective(captchas);
                throw new AppException("验证码错误", RequestCode.Form_Validate_Error);
            }
        }
        String str = ShiroUtils.md5(form.getLoginId(), form.getLoginPwd());
        // 密码错误
        if (!str.equals(account.getLoginPwd())) {
            loginLogs.setLoginState(2);
            this.recordLog(loginLogs);
            account.setErrorCount(account.getErrorCount() + 1);
            account.setLastLoginIP(ipL);
            account.setLastModifyTime(currentTime);
            // 错误四次锁定
            if (account.getErrorCount() + 1 >= ProjectConstant.LOCKEDCOUNT) {
                account.setState(3);
            }
            accountService.updateByPrimaryKeySelective(account);
            // 更新跟踪表 IP 计数器的 ErrorCount、LastErrorTim
            traces.setErrorCount(traces.getErrorCount() + 1);
            traces.setLastErrorTime(currentTime);
            loginTracesService.updateByPrimaryKeySelective(traces);
            throw new AppException("密码错误", RequestCode.Form_Validate_Error);
        }
        // 密码正确
        // 日志记录
        loginLogs.setLoginState(0);
        this.recordLog(loginLogs);
        // 重置 IP 和账号计数器的 ErrorCount 字段，重置账号的 state 字段；
        traces.setErrorCount(0);
        traces.setLastErrorTime(currentTime);
        loginTracesService.updateByPrimaryKeySelective(traces);
        // 重置管理员表erroCount 管理员跟踪表erroCount state = 0
        account.setErrorCount(0);
        account.setState(0);
        account.setLastLoginIP(ipL);
        account.setLastModifyTime(currentTime);
        accountService.updateByPrimaryKeySelective(account);
        // 生成token
        String token = CacheKeyPrefix.ACCOUNT_USER + StringEx.newUUID() + "_" + currentTime;
        // 获取权限下的用户信息
        UserSession userSession = this.getLoginDTO(account);
        // 返回的登录信息,拷贝属性对List报错，拷贝基础属性后，再赋值list
        // 将登录对象放入缓存
        redisUtil.set(token, userSession, 12L, TimeUnit.HOURS);
        loginDTO = new LoginDTO();
        loginDTO.setState(0);
        loginDTO.setToken(token);

        return loginDTO;
    }

    /**
     * 登录日志
     *
     * @param form
     * @param ip
     * @return
     */
    private LoginLogsDO loginLog(LoginForm form, long ip, String userAgent) {
        LoginLogsDO result = new LoginLogsDO();
        result.setLoginIP(ip);
        result.setHeaders(userAgent);
        result.setUri("/api/web/account/login");
        return result;
    }


    /**
     * 记录登录日志
     *
     * @return
     */
    public void recordLog(LoginLogsDO record) {
        // 登录日志表
        if (loginLogsService.insertSelective(record) < 1) {
            throw new AppException("日志记录失败", RequestCode.Form_Validate_Error);
        }
    }

    @Override
    public UserSession getLoginDTO(AccountsDO accountsDO) {
        UserSession userSession = new UserSession();
        userSession.setAccountId(accountsDO.getAccountId());
        userSession.setLoginId(accountsDO.getLoginId());
        userSession.setNickName(accountsDO.getNickname());
        userSession.setState(accountsDO.getState());
        // 获取所有角色
//        List<Integer> roles = rolesService.rolesAll(adminDO.getAdminId());
//        userSession.setRoleIdList(roles);
        // 获取所有权限 <objectType,<objectId,rights>>
//        Map<Integer, Map<Integer, Integer>> rights = this.rights(roles);
//        userSession.setRights(rights);
        return userSession;
    }


    @Override
    public UserSessionReturnDTO loginRenewal(String token) {
        UserSession userSession = new UserSession();
        redisUtil.set(userSession.getToken(), userSession, 12L, TimeUnit.HOURS);
        return null;
    }

    /**
     * 注销
     *
     * @param token
     * @return
     */
    @Override
    public void loginOut(String token) {
        // 移除redis缓存
        redisUtil.remove(token);
    }

    /**
     * 获取所有权限集合
     *
     * @param roles
     * @return
     */
    @Override
    public Map<Integer, Map<Integer, Integer>> rights(List<Integer> roles) {
        return rightsService.rights(roles);
    }

}
